package edu.neumont.pro180._2009_q2.batch;

import java.awt.Point;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import edu.neumont.pro180._2009_q2.Board;
import edu.neumont.pro180._2009_q2.ComplexCommand;
import edu.neumont.pro180._2009_q2.GamePieceMove;
import edu.neumont.pro180._2009_q2.Player;
import edu.neumont.pro180._2009_q2.Square;
import edu.neumont.pro180._2009_q2.chess.ChessGame;
import edu.neumont.pro180._2009_q2.chess.King;
import edu.neumont.pro180._2009_q2.chess.PawnPromotion;
import edu.neumont.pro180._2009_q2.chess.pieceFactory.PlayerFactory;
import edu.neumont.pro180._2009_q2.interfaces.ICommand;
import edu.neumont.pro180._2009_q2.interfaces.IGamePiece;

public class BatchChessGame extends ChessGame
{
	private static class BatchGame
	{
		private ICommand[] commands = null;
		private Player white;
		private Player black;
		private Board board;
		private Map<String,String> details;
	}
	
	private String batchFile = null;
	private ArrayList<BatchGame> games = null;
	private int lastPlayedGame = -1;
	private int moveDelay;
	
	@Override
	public void playGame() throws IllegalStateException
	{
		BatchGame game = getGame( ++lastPlayedGame );
		game.board.setActivePlayer(game.white);
		
		try
		{
			int move = 1;
			for( ICommand command : game.commands )
			{
				// Execute the command and wait
				command.execute();
				System.out.printf("\n\n\n%2d: (%s)\n", (move+1)/2, move % 2 == 1 ? "White" : "Black" );
				printBoard( game.board );
				Thread.sleep( moveDelay );
				move++;
			}
		}
		catch( InterruptedException e )
		{
			System.out.println("Execution interrupted. Aborting game.");
		}
	}
	
	public BatchChessGame()
	{
		this( 1000 );
	}
	
	public BatchChessGame( int moveDelay )
	{
		setDelay( moveDelay );
	}
	
	public int getDelay()
	{
		return moveDelay;
	}
	
	public void setDelay( int delay )
	{
		moveDelay = delay;
	}
	
	public int getGameCount()
	{
		getGame(0); // just to make sure the games array was initialized
		return games.size();
	}
	
	
	public void setBatchFile( String batchFilePath )
	{
		batchFile = batchFilePath;
		games = null;
	}
	
	private BatchGame getGame( int gameNumber )
	{
		if( games == null && !validateSettings() )
			throw new IllegalStateException("Batch file either wasn't specified or was invalid.");
		
		if( gameNumber < 0 || gameNumber >= games.size() )
			throw new IllegalArgumentException( "Cannot get a game that wasn't specified in the file." );
		
		return games.get( gameNumber );
	}
	
	@Override
	public boolean validateSettings()
	{
		boolean valid = true;
		if( batchFile == null )
			return false;
		
		if( games == null )
		{
			games = new ArrayList<BatchGame>();
			
			File f = new File( batchFile );
			if( f.isFile() )
			{
				try
				{
					Scanner scanner = new Scanner(f);
					scanner.useDelimiter( "$" );
					String contents = scanner.next();
					while( contents.length() > 0 )
						contents = parseBatchAndSetupGame( contents );
				}
				catch( FileNotFoundException e )
				{
					valid = false;
				}
			}
			else
				valid = false;
		}
		
		return valid && super.validateSettings();
	}
	
	private String parseBatchAndSetupGame( String contents )
	{
		BatchGame game = new BatchGame();
		
		game.white = PlayerFactory.getInstance().create();
		game.black = PlayerFactory.getInstance().create();
		
		game.board = new Board(this.rowCount, this.columnCount);
		game.board.addPlayer(game.white);
		game.board.addPlayer(game.black);
		populateChessBoard(game.board, game.white, game.black);
		game.board.setActivePlayer(game.white);
		
		Scanner lineScanner = new Scanner( contents );
		Map<String, String> details = new HashMap<String, String>();
		StringBuilder commands = new StringBuilder();
		
		// Get the headers
		boolean headersComplete = false;
		Pattern headerPattern = Pattern.compile("\\[(\\w+)\\s+\"(.*?)\"\\]");
		while( lineScanner.hasNext() && !headersComplete )
		{
			String line = lineScanner.nextLine().trim();
			if( line.length() > 0 )
			{
				Matcher headerMatcher = headerPattern.matcher(line);
				if( headerMatcher.find() )
					details.put(
							headerMatcher.group(1).toLowerCase(),
							headerMatcher.group(2));
			}
			else
			{
				headersComplete = true;
			}
		}
		game.details = details;
		if( game.details.containsKey("white") )
			game.white.name = details.get("white");
		if( game.details.containsKey("black") )
			game.black.name = details.get("black");
		
		// Get the game
		boolean gameComplete = false;
		while( lineScanner.hasNext() && !gameComplete )
		{
			String line = lineScanner.nextLine().trim();
			if( line.length() > 0 )
				commands.append(line + " ");
			else
				gameComplete = true;
		}
		
		// Load/Interpret Commands
		if( interpretCommands(game, commands.toString()) 
				&& game.commands != null 
				&& game.commands.length > 0 )
			games.add( game );
		
		// Get the remaining games
		String stillToParse = "";
		//lineScanner.useDelimiter("$");
		//if( lineScanner.hasNext() )
		//	stillToParse = lineScanner.next();
		return stillToParse;
	}
	
	private boolean interpretCommands( BatchGame game, String commandString )
	{
		ArrayList<ICommand> commands = new ArrayList<ICommand>();
		
		String validMoveChars = "KQRBNPa-h1-8x\\+=O\\-";
		Pattern movePattern = Pattern.compile( 
				"(\\d+)\\.\\s+(["+validMoveChars+"]+)\\s+(["+validMoveChars+"]+)" );
		Matcher moveMatcher = movePattern.matcher(commandString);
		
		int lastMoveNumber = 0;
		String whiteMove = "";
		String blackMove = "";
		while( moveMatcher.find() )
		{
			// Check to see if we misread the last move
			// -- For the game's very last move, black may or may not be able to go
			//    but for all other moves, we should have both white and black
			if( whiteMove == null || blackMove == null )
				return false;
			
			// Check to see if we missed a number
			int moveNumber = Integer.parseInt(moveMatcher.group(1));
			if( moveNumber != lastMoveNumber+1 )
				return false;
			lastMoveNumber = moveNumber;
			
			// Get the moves and parse them into an actual command
			whiteMove = moveMatcher.group(2);
			blackMove = moveMatcher.group(3);

			// Parse the white move
			ICommand whiteCommand = parseMove(game.board, whiteMove);
			if( whiteCommand == null )
				whiteMove = null; // bad stuff happened, so pretend I never got a white move
			else
				commands.add(whiteCommand);
			
			// Parse the black move
			ICommand blackCommand = parseMove(game.board, blackMove);
			if( blackCommand == null )
				blackMove = null; // bad stuff happened, so pretend I never got a black move
			else
				commands.add(blackCommand);
				
		}
		
		// Move back up the stack (reset the game)
		for( int i=commands.size()-1; i >= 0; i-- )
			commands.get(i).undo();
		game.commands = commands.toArray(new ICommand[0]);
		
		return true;
	}
	
	private ICommand parseMove( Board board, String move )
	{
		if( move == null )
			return null;
		
		ICommand command = null;
		String castlePattern = "(O-O(?:-O)?)";
		String pawnPromotionPattern = "([a-h][1-8]=[QRBN])";
		String standardMovePattern = "(?:([KQRBNP])?([a-h])?([1-8])?x?([a-h][1-8])\\+?)";
		Pattern commandPattern = Pattern.compile( String.format( "(?:%s|%s|%s)", castlePattern, pawnPromotionPattern, standardMovePattern ) );
		Matcher commandMatcher = commandPattern.matcher(move);
		
		if( commandMatcher.find() )
		{
			// Castling
			if( commandMatcher.group(1) != null )
			{
				ArrayList<IGamePiece> kings = getPieces(board, selectedPieceViews.get(King.class));
				if( kings == null || kings.size() != 1 )
					return null; // something SERIOUSLY went wrong
				IGamePiece king = kings.get(0);
				Square kingSquare = king.getSquare();
				Point kingOffset = null;
				IGamePiece castle;
				Square castleSquare = null;
				Point castleOffset = null;

				// Queen-side castle
				if( commandMatcher.group(1).toUpperCase().equals("O-O-O") )
				{
					castleSquare = king.getSquare().getNeighbor(new Point(-4,0));
					castle = castleSquare.getGamePiece();
					kingOffset = new Point(-2,0);
					castleOffset = new Point(3,0);
				}
				// King-side castle
				else
				{
					castleSquare = king.getSquare().getNeighbor(new Point(3,0));
					castle = castleSquare.getGamePiece();
					kingOffset = new Point(2,0);
					castleOffset = new Point(-2,0);
				}
				ICommand kingMove = new GamePieceMove( king, kingSquare.getNeighbor(kingOffset) );
				ICommand castleMove = new GamePieceMove( castle, castleSquare.getNeighbor(castleOffset) );
				command = new ComplexCommand( kingMove, castleMove );
			}
			
			// Pawn Promotion
			else if( commandMatcher.group(2) != null )
			{
				char promotion = commandMatcher.group(2).charAt(commandMatcher.group(2).length()-1);
				Class<? extends IGamePiece> pieceClass = getPieceClass(Character.toString(promotion));
				if( pieceClass == null )
					return null; // Couldn't figure out who to promote to, I guess?
				
				String destination = commandMatcher.group(2).substring(0,2);
				Point destPoint = coordsToPoint(destination);
				Square square = board.getSquare(destPoint);
				IGamePiece pawn = getPiece( board, "P", square, null );
				command = new PawnPromotion( pawn.getSquare(), square, pieceClass );
			}
			
			// Regular Move
			else
			{
				int PIECE = 3;
				int START_ROW = PIECE+1;
				int START_COL = START_ROW+1;
				int DESTINATION = START_COL+1;
				
				String pieceName = "P";
				if( commandMatcher.group(PIECE) != null )
					pieceName = commandMatcher.group(PIECE);
				
				String startRow = null;
				if( commandMatcher.group(START_ROW) != null )
					startRow = commandMatcher.group(START_ROW);
				
				String startCol = null;
				if( commandMatcher.group(START_COL) != null )
					startCol = commandMatcher.group(START_COL);
				
				String destination = commandMatcher.group(DESTINATION);

				// Get the square
				Point destinationPoint = coordsToPoint(destination);
				Square destinationSquare = board.getSquare(destinationPoint);
				
				// Get the piece
				Point start = null;
				if( startRow != null || startCol != null )
					start = coordsToPoint( 
						(startRow != null ? startRow : "I") + (startCol != null ? startCol : "9") );
				IGamePiece piece = getPiece(board, pieceName, destinationSquare, start );
				if( piece != null && destinationSquare != null )
				{
					if( destinationSquare.getGamePiece() != null )
						command = new ComplexCommand(
							destinationSquare.getGamePiece().getMove(null),
							piece.getMove(destinationSquare) );
					else
						command = piece.getMove(destinationSquare);
				}
			}
		}

		
		if( command != null )
		{
			command.execute();
			board.nextPlayer();
		}
		return command;
	}
	
	private Class<? extends IGamePiece> getPieceClass( String pieceName )
	{
		// Get the class of the piece
		Class<? extends IGamePiece> pieceClass = null;
		for( Class<? extends IGamePiece> c : selectedPieceViews.keySet() )
		{
			if( selectedPieceViews.get(c).equals(pieceName) )
			{
				pieceClass = c;
				break;
			}
		}
		return pieceClass;
	}
	
	private ArrayList<IGamePiece> getPieces( Board board, String pieceName )
	{
		// Get the class of the piece
		Class<?> pieceClass = getPieceClass(pieceName);
		
		// Get the pieces based on the piece name and the possible moves
		ArrayList<IGamePiece> matchingPieces = new ArrayList<IGamePiece>();
		Collection<IGamePiece> pieces = board.getActivePlayer().getActivePieces();
		for( IGamePiece possible : pieces )
		{
			if( possible.getClass() == pieceClass )
			{
				matchingPieces.add(possible);
			}
		}
		
		return matchingPieces;
	}

	private IGamePiece getPiece( Board board, String pieceName, Square destination, Point start )
	{
		Collection<IGamePiece> potentialPieces = getPieces(board, pieceName);
		IGamePiece matchingPiece = null;
		
		// Filter the list based on the potential moves and start row/column (if there is more than one)
		for( IGamePiece piece : potentialPieces )
		{
			Collection<Square> possibleMoves = piece.getPossibleSquares();
			Square square = piece.getSquare();
			if( possibleMoves.contains(destination) && (start == null || board.isLinear(square, start)) )
			{
				if( matchingPiece == null )
					matchingPiece = piece;
				else
					return null;
			}
		}
		// printBoard(board);
		
		return matchingPiece;
	}	
}
